/*
 * Author: Wojciech Walczak <wojtek.gminick.walczak@gmail.com>
 * Copyright 2008 Wojciech Walczak
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include "Python.h"

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <utmp.h>

#include "proc/readproc.h"
#include "proc/devname.h"
#include "proc/sysinfo.h"

#include "procpsver.h"

static PyObject *WUtmpError;

extern proc_t** readproctab(int flags, ... /* same as openproc */ );

/* openproc() flags */
int setflags(void)
{
   int flags;
   flags =  PROC_FILLUSR;
   flags |= PROC_FILLGRP;
   flags |= PROC_FILLCOM;
   flags |= PROC_FILLENV;
   flags |= PROC_FILLSTAT;
   flags |= PROC_FILLSTATUS;
   flags |= PROC_FILLMEM;
   flags |= PROC_FILLARG;
   flags |= PROC_FILLWCHAN;

   return flags;
}


/* stat the device file to get an idle time; based on procps's w.c */
time_t
idletime(char *tty)
{
   struct stat sbuf;
   
   if(stat(tty, &sbuf) != 0)
      return 0;

   return time(NULL) - sbuf.st_atime;
}

PyObject *timetuple(struct tm * tm)
{
   return Py_BuildValue("(iiiiiiiii)",
                                   tm->tm_year+1900,
                                   tm->tm_mon+1,
                                   tm->tm_mday,
                                   tm->tm_hour,
                                   tm->tm_min,
                                   tm->tm_sec,
                                   tm->tm_wday,
                                   tm->tm_yday+1,
                                   tm->tm_isdst);

}

/* nie drukuje czasu! XXX */
PyObject *
cputime(time_t t, int centi_sec)
{
   PyObject *time7;

   if((long)t < (long)0)
      return PyString_FromString("?");
   
   if(t >= 48*60*60)                          /* > 2 days */
      time7 = PyString_FromFormat(" %2ludays", t/(24*60*60));
   else if (t >= 60*60)                        /* > 1 hour */
      time7 = PyString_FromFormat(" %2lu:%02um", t/(60*60), (unsigned) ((t/60)%60));
   else if (t > 60)                            /* > 1 minute */
      time7 = PyString_FromFormat(" %2lu:%02um", t/60, (unsigned) t%60);
   else
      time7 = PyString_FromFormat(" %2lu.%02us", t, centi_sec);

   return time7;
}

/* nie oblicza idle'a! XXX */
PyObject *
idlefmt(time_t t)
{
   char *time7 = malloc(8);

   if(time7==NULL) {
      PyErr_NoMemory();
      return NULL;
   }

   if((long)t < (long)0) {  /* system clock changed? */
      printf("   ?   ");
      return NULL;
   }

   if(t >= 48*60*60)                          /* > 2 days */
      sprintf(time7, " %2ludays", t/(24*60*60));
   else if(t >= 60*60)                        /* > 1 hour */
      sprintf(time7, " %2lu:%02um", t/(60*60), (unsigned) ((t/60)%60));
   else if (t > 60)                            /* > 1 minute */
      sprintf(time7, " %2lu:%02u ", t/60, (unsigned) t%60);
   else
      sprintf(time7, " %2lu.00s", t);

   return PyString_FromString(time7);
}

/*  based on procps's w.c */
PyObject *
logintime(time_t logt)
{
   struct tm *tm;

   tm = localtime(&logt);

   if(tm == NULL)
      return NULL;

   return timetuple(tm);
}

char *
best_what(struct utmp *u, char *tty, unsigned long long *jcpu, unsigned long long *pcpu, int xdm)
{
   int line;
   proc_t **pptr;
   proc_t *best = NULL;
   proc_t *secondbest = NULL;
   proc_t *xdmbest = NULL;

   pptr = readproctab(setflags());
         //PROC_FILLCOM|PROC_FILLUSR|PROC_FILLSTAT|PROC_FILLSTATUS);

   if(tty!=NULL)
      line = tty_to_dev(tty);

   *jcpu = 0;

   for(; *pptr; pptr++) {
      proc_t *tmp = *pptr;

      if(xdm) {
#if defined(PROCPSVER30) || defined(PROCPSVER31)
         if(tmp->pid == u->ut_pid)
#else
         if(tmp->tid == u->ut_pid)
#endif
            xdmbest = tmp;
      }

#ifndef PROCPSVER30
      if(tmp->tgid == u->ut_pid) {
         best = tmp;
      }
#endif

      // ut_line zawieralo jakas rozsadna wartosc
      if(tty!=NULL) {
         if(tmp->tty != line)
            continue;
      }
      else { 
         if (strcmp(u->ut_user, tmp->ruser)!=0)
            continue;
      }

      (*jcpu) += tmp->utime + tmp->stime;
      secondbest = tmp;

      if(!(secondbest && tmp->start_time <= secondbest->start_time))
         secondbest = tmp;
#ifndef PROCPSVER30
      if(tmp->tgid != tmp->tpgid)
         continue;
#endif
      if(best && tmp->start_time <= best->start_time)
         continue;

      best = tmp;
   }
   
   if(xdm && xdmbest) {
      (*pcpu) = xdmbest->utime + xdmbest->stime;
      return xdmbest->cmdline[0];
   }
   if(best && best->cmdline) {
      (*pcpu) = best->utime + best->stime;
      return best->cmdline[0];
   }
   else if(secondbest && secondbest->cmdline) {
      (*pcpu) = secondbest->utime + secondbest->stime;
      return secondbest->cmdline[0];
   }
//   return best->cmdline?best->cmdline[0]:secondbest->cmdline[0];
   return NULL;
}

PyDoc_STRVAR(wgetutent_doc, "wgetutent() -> returns list of dictionaries\n\n\
Each dictionary contains following keys:\n\
   \"user\"     => user name\n\
   \"host\"     => hostname for remote login\n\
   \"line\"     => device name of tty - \"/dev/\"\n\
   \"pid\"      => PID of login process\n\
   \"idle\"     => idle time as a tuple:\n\
                 (year, month, day, hour, min, sec, wday, yday, isdst)\n\
   \"login\"    => login time as a tuple:\n\
                 (year, month, day, hour, min, sec, wday, yday, isdst)\n\
   \"jcpu\"     => the time used by all processes attached to the tty\n\
   \"pcpu\"     => the time used by the current process (value of 'process' key)\n\
   \"process\"  => process name (without args, just argv[0])\n\
\n\
Data in this dictionary isn't exactly getutent(3) data; it adds some data\n\
just as w(1) does.\n\n\
NOTE: In case of xdm running jcpu won't return the time used by all processes\n\
attached to the tty. It'll calculate the time used by all processes of a given\n\
user (not including those attached to any other tty of the same user).\n\n\
Value of 'process' key may not correspond with 'pid' key. This module tries\n\
to find the name of the process that is running at the momment, while  'pid'\n\
key keeps pid of login process gained by getutent(3) call.");

PyObject *wutmp_wgetutent(PyObject *self, PyObject *args)
{
   struct utmp *u=malloc(sizeof(struct utmp));
   PyObject *list = PyList_New(0);
   PyObject *o;
   char tty[32] = "";
   time_t t;
   int xdm=0;
   char *bestname;
   unsigned long long jcpu, pcpu;

   if(!PyArg_ParseTuple(args, ""))
      return NULL;

   setutent();
   while(1) {
      xdm=0;
      u = getutent();
      if(!u) break;
      if(u->ut_type != USER_PROCESS) continue;

      /* we can't calculate idle for xdm logins */
      if(*u->ut_line == ':') {
         xdm=1;
         t=0;
         bestname = best_what(u, NULL, &jcpu, &pcpu, xdm);
      }
      else {       
         memset(tty, 0, sizeof(tty));
         strncat(tty, "/dev/", sizeof(tty));
         strncat(tty, u->ut_line, sizeof(tty));
         t = idletime(tty);
         bestname = best_what(u, tty+5, &jcpu, &pcpu, xdm);
      }

      o = Py_BuildValue("{"
                           "s:s,s:s,s:s,"
                           "s:i,"
                           "s:O,s:O,s:O,s:O,"
                           "s:s"
                        "}",
                    "user", u->ut_user,
                    "host", u->ut_host,
                    "line", u->ut_line,
                     "pid", u->ut_pid,
                     /* idlefmt zwraca tuple, a jak xdm==True to stringa! XXX*/
                    "idle", xdm ? PyString_FromString(" ?xdm? ") : idlefmt(t),
                   "login", logintime(u->ut_time),
                    "jcpu", cputime(jcpu/Hertz, (jcpu%Hertz)*(100./Hertz)),
                    "pcpu", cputime(pcpu/Hertz, (pcpu%Hertz)*(100./Hertz)),
                 "process", bestname);

      if(o == NULL)
         return NULL;

      PyList_Append(list, o);
   }
   return list;
}


static PyMethodDef wutmpMethods[] = {
    {"wgetutent", wutmp_wgetutent, METH_VARARGS, wgetutent_doc},
    {NULL, NULL, 0, NULL}
};


PyMODINIT_FUNC
initwutmp(void)
{
   PyObject *m, *d;

   m = Py_InitModule("wutmp", wutmpMethods);
   d = PyModule_GetDict(m);
   WUtmpError = PyErr_NewException("wutmp.error", NULL, NULL);
   Py_INCREF(WUtmpError);
   PyModule_AddObject(m, "error", WUtmpError);
}
